﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using CRL.Core;
using System.Reflection;
using CRL;
using System.Collections.Concurrent;

namespace CRL.LambdaQuery
{
    public partial class ExpressionVisitor
    {
        CRLExpression.CRLExpression MethodCallExpressionHandler(Expression exp, ExpressionType? nodeType = null, bool firstLevel = false)
        {
            #region methodCall
            MethodCallExpression mcExp = (MethodCallExpression)exp;
            var methodName = mcExp.Method.Name;
            var relationMethods = new string[] { "Any", "Where" };
            if (mcExp.Arguments.Count > 1 && relationMethods.Contains(methodName))
            {
                var firstType = mcExp.Arguments.First().Type;
                if (firstType.IsClass && firstType != typeof(string))
                {
                    var type3 = mcExp.Arguments[1].GetType();
                    var method3 = type3.GetProperty("Body");
                    var exp3 = method3.GetValue(mcExp.Arguments[1]);
                    //if (methodName != "Any" && methodName != "Where")
                    //{
                    //    throw new Exception($"内关联不支持的方法:{methodName}");
                    //}
                    var exp2 = RouteExpressionHandler(exp3 as Expression);
                    var types2 = firstType.GetGenericArguments();
                    bool withOne = true;
                    if (types2.Length > 0)
                    {
                        firstType = types2.First();
                        withOne = false;
                    }
                    var rf = AbsPropertyBuilder.FindRelationInfo(lambdaQueryBase.__MainType, firstType);
                    if (rf == null)
                    {
                        throw new Exception($"未找到关系配置:{lambdaQueryBase.__MainType} {firstType}");
                    }
                    var bq = lambdaQueryBase;
                    bq.FormatJoinExpression(rf.parameters, rf.expression, out var joinExp);
                    if (withOne)
                    {
                        bq.AddInnerRelation(new TypeQuery(firstType), JoinType.Inner, joinExp);
                        return exp2;
                    }
                    else
                    {
                        // and t.id in(select t.id from t1 inner join  t2 on t1.id=t2.id where t2.name='args')
                        var parameNames = rf.parameters.Select(b => b.Name).ToArray();
                        var prefix1 = bq.GetPrefix(rf.Type1, parameNames[0]);
                        var prefix2 = bq.GetPrefix(rf.Type2, parameNames[1]);
                        var sb = new StringBuilder();
                        sb.Append($"{prefix1}{rf.Type1Field.MapingName} in(");
                        sb.Append($"select {prefix1}{rf.Type1Field.MapingName} from {TypeCache.GetTableName(rf.Type1, dbContext)} {prefix1.Replace(".", "")}");
                        sb.Append($" inner join {TypeCache.GetTableName(rf.Type2, dbContext)} {prefix2.Replace(".", "")}");
                        sb.Append($" on {joinExp.SqlOut} where {exp2.SqlOut})");
                        return new CRLExpression.CRLExpression() { Type = CRLExpression.CRLExpressionType.Tree, SqlOut = sb.ToString(), Data = sb.ToString() };
                    }
                }
            }
            var arguments = new List<object>();
            var allArguments = new List<Expression>(mcExp.Arguments);
            int argsIndex = 0;
            Expression firstArgs;
            bool isLambdaQueryJoinExt = false;
            string typeParameterName = "";
            if (mcExp.Object == null)//区分静态方法还是实例方法
            {
                firstArgs = allArguments[0];//like b.Name.IsNull("22")
                argsIndex = 1;
                if (firstArgs.Type.Name.Contains("LambdaQueryJoin"))
                {
                    isLambdaQueryJoinExt = true;
                }
            }
            else
            {
                firstArgs = mcExp.Object;//like b.Id.ToString()
                if (allArguments.Count > 0 && (allArguments[0] is MemberExpression))//like ids.Contains(b.Id)
                {
                    var mexp2 = allArguments[0] as MemberExpression;
                    var firstArgsM = firstArgs as MemberExpression;
                    //var par2 = (ParameterExpression)mexp2.Expression;
                    if (mexp2.Expression is ParameterExpression && (firstArgsM.Expression is ConstantExpression || firstArgsM.Expression is MemberExpression))
                    {
                        firstArgs = allArguments[0];
                        argsIndex = 1;
                        allArguments.Add(mcExp.Object);
                        if (firstArgs.Type.Name.Contains("LambdaQueryJoin"))
                        {
                            isLambdaQueryJoinExt = true;
                        }
                    }
                }
            }
            #region MethodCallExpression
            //bool isConstantMethod = false;

            MemberExpression memberExpression = null;

            string methodField = "";
            string memberName = "";
        label2:
            if (firstArgs is ParameterExpression || isLambdaQueryJoinExt)
            {
                if (mcExp.Arguments.Count > 1)
                {
                    var exp2 = mcExp.Arguments[1] as UnaryExpression;//like b.SUM(x=>x.Id * x.Number)
                    if (exp2 != null)
                    {
                        var type = exp2.Operand.GetType();
                        var p = type.GetProperty("Body");
                        var exp3 = p.GetValue(exp2.Operand, null) as Expression;
                        methodField = RouteExpressionHandler(exp3).SqlOut;
                    }
                    else// like b.sum("222") 按变量参数
                    {
                        firstArgs = mcExp.Arguments[1];
                        for (int i = 1; i < mcExp.Arguments.Count; i++)
                        {
                            var obj = GetParameExpressionValue(allArguments[i], out var isConstant2);
                            arguments.Add(obj);
                        }
                        allArguments.RemoveAt(0);
                        goto label2;
                    }
                }
                memberName = "";
            }
            else if (firstArgs is UnaryExpression)//like a.Code.Count()
            {
                memberExpression = (firstArgs as UnaryExpression).Operand as MemberExpression;
                memberName = memberExpression.Member.Name;
                var field = TypeCache.GetProperties(memberExpression.Expression.Type, true)[memberName];
                memberName = __DBAdapter.FieldNameFormat(field);
                typeParameterName = (memberExpression.Expression as ParameterExpression)?.Name;
                methodField = FormatFieldPrefix(memberExpression.Expression.Type, typeParameterName, memberName);
            }
            else if (firstArgs is BinaryExpression)
            {
                var be = firstArgs as BinaryExpression;
                methodField = BinaryExpressionHandler(be.Left, be.Right, be.NodeType).Data.ToString();
            }
            else if (firstArgs is MemberExpression)
            {
                //like a.Code
                memberExpression = firstArgs as MemberExpression;
                memberName = memberExpression.Member.Name;
                var type = memberExpression.Expression.Type;
                if (type.IsSubclassOf(typeof(IModel)))
                {
                    var field = TypeCache.GetProperties(type, true)[memberExpression.Member.Name];
                    memberName = __DBAdapter.FieldNameFormat(field);
                }
                if (memberExpression.Expression.NodeType == ExpressionType.Parameter)
                {
                    typeParameterName = (memberExpression.Expression as ParameterExpression)?.Name;
                    methodField = FormatFieldPrefix(memberExpression.Expression.Type, typeParameterName, memberName);
                    //var allConstant = true;
                    for (int i = argsIndex; i < allArguments.Count; i++)
                    {
                        bool isConstant2;
                        var obj = GetParameExpressionValue(allArguments[i], out isConstant2);
                        arguments.Add(obj);
                    }
                }
                else
                {
                    //like Convert.ToDateTime(times)
                    var obj = ConstantValueVisitor.GetParameExpressionValue(firstArgs);
                    arguments.Add(obj);
                    for (int i = argsIndex; i < allArguments.Count; i++)
                    {
                        bool isConstant2;
                        var obj2 = GetParameExpressionValue(allArguments[i], out isConstant2);
                        arguments.Add(obj2);
                    }
                }
            }
            else if (firstArgs is ConstantExpression)//按常量
            {
                //like DateTime.Parse("2016-02-11 12:56"),
                //isConstantMethod = true;
                var obj = ConstantValueVisitor.GetParameExpressionValue(firstArgs);
                arguments.Add(obj);
            }
            //else
            //{
            //    throw new CRLException("不支持此语法解析:" + args);
            //}

            if (nodeType == null)
            {
                nodeType = ExpressionType.Equal;
            }
            if (string.IsNullOrEmpty(methodField))
            {
                //当是常量转换方法

                var method = mcExp.Method;
                object obj = null;
                if (method.IsStatic)//like DateTime.Parse("2016-02-11")
                {
                    if (method.IsDefined(typeof(System.Runtime.CompilerServices.ExtensionAttribute), false))//扩展方法,like public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value)
                    {
                        if (arguments.Count > 1)
                        {
                            if (arguments[1] is ExpressionValueObj)
                            {
                                var valueObj = (ExpressionValueObj)arguments[1];
                                if (valueObj == null)
                                {
                                    throw new Exception("不支持此语法:" + mcExp);
                                }
                                memberName = valueObj.member.Name;
                                arguments = new List<object>() { arguments[0] };
                                methodField = valueObj.Value.ToString();
                            }
                        }
                        goto lable1;
                    }
                    else
                    {
                        obj = method.Invoke(null, arguments.ToArray());
                    }
                }
                else//like time.AddDays(1)
                {
                    //if (arguments.Count == 0)
                    //{
                    //    throw new Exception("未能解析" + exp);
                    //}
                    obj = MethodCallInvokeBase(mcExp, out var target);
                    if (mcExp.Object != null && typeof(IQuery).IsAssignableFrom(mcExp.Object.Type))
                    {
                        var methodQuery = ((IQuery)target).GetQuery();
                        methodQuery = $"({methodQuery})";
                        return new CRLExpression.CRLExpression() { Type = CRLExpression.CRLExpressionType.Binary, Data = methodQuery };
                    }
                    //var args1 = arguments.First();
                    //arguments.RemoveAt(0);
                    //if (arguments.Count > 0)
                    //{
                    //    if (arguments[0] is ExpressionValueObj)
                    //    {
                    //        throw new Exception("不支持这样的语法:" + exp);
                    //    }
                    //}
                    //obj = method.Invoke(args1, arguments.ToArray());
                }
                var exp2 = new CRLExpression.CRLExpression() { Type = CRLExpression.CRLExpressionType.Value, Data = obj };

                return exp2;
            }
        lable1:
            var methodInfo = new CRLExpression.MethodCallObj() { Args = arguments, ExpressionType = nodeType.Value, MemberName = memberName, MethodName = methodName, MemberQueryName = methodField };
            methodInfo.ReturnType = mcExp.Type;

            #endregion
            if (memberExpression != null)
            {
                typeParameterName = (memberExpression.Expression as ParameterExpression)?.Name;
            }
            var exp4 = new CRLExpression.CRLExpression() { Type = CRLExpression.CRLExpressionType.MethodCall, Data = methodInfo, typeParameterName = typeParameterName };

            return exp4;
            #endregion
        }
    }
}
